S14-05 SSR-Next13
[TOC]
概述
什么是 Next.js
Next.js 是一个React 框架,支持CSR、SSR、SSG、ISR (Incremental Static Regeneration)等渲染模式。
Next.js 提供了创建 Web 应用程序的构建块,比如:用户界面、路由、数据获取、渲染模式、后端服务等等。
Next.js 不但处理 React 所需的工具和配置,还提供额外的功能和优化,比如:
- UI 构建, CSR、SSR、SSG、ISR 渲染模式,Routing、Data Fetching 等等。
中文官网:https://www.nextjs.cn/docs/getting-started
英文官网:https://nextjs.org/docs/getting-started
发展史
Next.js 于 2016 年 10 月 25 日首次作为开源项目发布在 GitHub 上,最初是基于六个原则开发的:
- 开箱即用、无处不在的 JS、所有函数用 JS 编写、自动代码拆分和服务器渲染、可配置数据获取、预期请求和简化部署。
Next.js 2.0 于 2017 年 3 月发布,改进后的版本让小型网站的工作变得更加容易,还提高了构建和热模块替换效率。
7.0 版于 2018 年 9 月发布,改进了错误处理并支持 React 的上下文 API。升级到了webpack4。
8.0 版于 2019 年 2 月发布,第一个提供 Serverless 部署的版本。
2020 年 3 月发布的 9.3 版包括各种优化和全局 Sass和 CSS 模块支持。
2020 年 7 月 27 日,Next.js 9.5 版发布,增加了增量静态再生成、重写和重定向支持等新功能。
2021 年 6 月 15 日,Next.js 版本 11 发布,其中包括:Webpack5 支持。
2021 年 10 月 26 日,Next.js 12 发布,添加了 Rust 编译器,使编译速度更快。
2022 年 10 月 26 日,Vercel 发布了 Next.js 13。
- 带来了一种新的路由模式,增加了 app 目录 、布局、服务器组件和一组新的数据获取方法等。
- 编译和压缩等由 Babel + Terser 换为 SWC( Speedy Web Compiler ),构建工具增加了 Turbopack。
特点
开箱即用,快速创建:
Next.js 已经帮我集成好了各种技术栈,比如:React、webpack、路由、数据获取、SCSS、TypeScript 等。
也提供了专门的脚手架:create-next-app。
约定式路由:
- Next.js 和 Nuxt3 一样,所有的路由都是根据 pages 目录结构自动生成。但在 Next.js 13 beta 版本增加了 app 目录。
内置 CSS 模块和 Sass 支持:
- 自从 Next.js 9.3 以后就内置了 CSS 模块和 Sass 支持,也是开箱即用。
全栈开发能力:
- Next.js 不但支持前端开发,还支持编写后端代码,比如:可开发登录验证、存储数据、获取数据等接口。
多种渲染模式: 支持 CSR、SSR、SSG、ISR 等渲染模式,当然也支持混合搭配使用。
利于搜索引擎优化:
- Next.js 支持使用服务器端渲染,同时它也是一个很棒的静态站点生成器,非常利于 SEO 和首屏渲染。
对比 Nuxt3
相同点:
- 利于搜索引擎优化,都能提高首屏渲染速度
- 零配置,开箱即用
- 都支持目录结构即路由、支持数据获取、支持TypeScript
- 服务器端渲染、静态网站生成、客户端渲染等
- 都需要 Node.js 服务器,支持全栈开发
区别:
Nuxt3 使用的是Vue 技术栈:Vue、webpack、vite、h3、nitro、node.....
Next.js 使用的是React 技术栈:React 、webpack 、express 、node.....
Nuxt3 支持组件、组合 API、Vue API 等自动导入
Next.js 不支持
Next.js 社区生态、资源和文档都会比 Nuxt3 友好(star 数: Nuxt3 -> 41.6k 和 Next.js -> 96.8k )
如何选择:
- 首先根据自己擅长的技术栈来选择,擅长 Vue 选择 Nuxt3,擅长 React 选择 Next.js
- 需要更灵活的,选择Next.js
- 需要简单易用、快速上手的,选择Nuxt3
基础
环境搭建 create-next-app
前置环境:
- Node.js:18.18 以上
- 前置环境:window 下可以用其随附的 Git Bash 终端命令
- 文本编辑器:Visual Studio Code
新建项目:
1、创建一个项目,项目名不支持大写
# npx(推荐)
npx create-next-app@latest
# pnpm(推荐)
pnpm dlx create-next-app@latest --typescript
# yarn
yarn create next-app --typescript
# pnpm
pnpm create next-app –-typescript
# npm
npm i create-next-app@latest –g && create-next-app
2、通过pnpm dev
运行项目
目录结构
入口文件 _app.tsx
_app.tsx 是项目的入口组件,主要作用:
- 扩展自定义的布局 Layout
- 引入全局样式
- 引入 Redux 状态管理
- 引入主题组件等等
- 全局监听客户端路由的切换
配置
路径别名 tsconfig.json
Next.js 默认是没有配置路径别名的,我们可以在 ts.config.json 中配置模块导入的别名:
- baseUrl :配置允许直接从项目的根目录导入,比如:
import Button from 'components/button'
- paths:允许配置模块别名,比如:
import Button from '@/components/button'
注意:如生效可以重启编辑器
next15: next15默认已经配置好了路径别名
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
环境变量
定义环境变量 .env
定义方式:
.env
:所有环境下生效的默认设置.env.development
:执行 next dev 时加载并生效.env.production
: 执行 next start 时加载并生效.env.xxx.local
:始终覆盖上面文件定义的默认值。常用于存储敏感信息。所有环境生效,通常只需一个.env.local
文件就够了。
语法:
- 大写单词,多个单词使用下划线,比如:
DB_HOST=localhost
- 添加
NEXT_PUBLIC_
前缀会额外暴露给浏览器,比如:NEXT_PUBLIC_ANALYTICS_ID=aaabbbccc
- 支持变量,例如
$PORT
// 只能在服务端获取
NAME = localhost
PORT = 8888
HOSTNAME = http://127.0.0.1
// 添加NEXT_PUBLIC_前缀的变量可以在客户端获取
NEXT_PUBLIC_BASE_URL = http://localhost:9999
// 支持变量$XXX
NEXT_PUBLIC_URL = http://$NAME:$PORT
获取环境变量 process.env
获取: process.env.xxx
.env
文件中定义环境变量会加载到 process.env
中。两端都可直接通过 process.env.xxx
访问使用(不支持解构)(没有代码提示)。
1、默认环境变量只能在服务端访问
2、添加NEXT_PUBLIC_
前缀的变量可以在客户端访问
▸ 判断客户端、服务端: typeof window === 'object'
Next中通过typeof window === 'object'
是否存在window对象的方式判断客户端、服务端环境。
注意: Next
中不支持process.server
或process.client
的方式判断运行环境。
▸ 判断开发环境、生产环境: process.env.NODE_ENV
Next中通过process.env.NODE_ENV
判断开发环境、生产环境。development:开发环境;production:生产环境。
注意:
- 由于 .env、.env.development 和 .env.production 文件定义了默认设置,需提交到源码仓库中。
- 而
.env.xxx.local
应当添加到.gitignore
中,因为这类文件是需要被忽略的。
Next配置 next.config.ts
next.config.ts 配置文件位于项目根目录,可对 Next.js 进行自定义配置,比如,可以进行如下配置:
- reactStrictMode: 是否启用严格模式,辅助开发,避免常见错误,例如:可以检查过期 API 来逐步升级
- env:配置环境变量,配置完需要重启
- 会添加到 process.env.xx 中
- 配置的优先级: next.config.js 中的 env > .env.local > .env
- basePath:要在域名的子路径下部署 Next.js 应用程序,您可以使用 basePath 配置选项。
- basePath:允许为应用程序设置 URl路由前缀。
- 例如 basePath=/music, 即用 /music 访问首页,而不是默认 /
- images:配置图像优化,包括域名白名单。
- swcMinify: 用 Speedy Web Compiler 编译和压缩技术,而不是 Babel + Terser 技术
- experimental:启用实验性功能,例如
appDir
。
更多配置: https://nextjs.org/docs/api-reference/next.config.js/introduction
▸ 示例:
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {
// 启用严格模式
reactStrictMode: true,
// 配置图片 URL 的白名单
images: {
remotePatterns: [
{
protocol: "https",
hostname: "**.music.126.net"
}
]
},
// 设置环境变量
env: {
CUSTOM_ENV: 'value'
},
// 启用实验性功能
experimental: {
appDir: true
}
// 设置路由前缀
basePath: '/music'
}
export default nextConfig
内置组件
API
Next.js 框架也提供了几个内置组件,常用的有:
<Head>:用于将新增的标签添加到页面的 head 标签中,从
next/head
中导入。- html
import Head from 'next/head'; const MyPage = () => { return ( <> <Head> <title>我的页面标题</title> <meta name="description" content="这是我的页面描述" /> <link rel="icon" href="/favicon.ico" /> </Head> <main> <h1>欢迎来到我的页面!</h1> </main> </> ); }; export default MyPage;
<Link>:可以启用客户端的路由切换,从
next/link
导入。属性:
href:
string | {pathname:'' , query : {}}
,要导航到的路径或 URL。replace?:
boolean
,默认:false
,是否替换当前 url 页面,而不是将新的 url 添加到堆栈中。target?:
_blank | _self | _parent | _top
,指定何种方式显示新页面,同a标签中的target一样。as?:
string
,在浏览器的URL栏显示的路径的别名。prefetch:
boolean
,默认:false
,当<Link>
组件进入用户的视区(最初或通过滚动)时,将发生预取。预取仅在 production 中启用。其他:scroll、shallow、locale等
- tsx
<Link href="/dashboard">Dashboard</Link> <Link href={{ pathname: '/about', query: { name: 'test' }, }}>About</Link> <Link href="/dashboard" replace>Dashboard</Link> <Link href="/dashboard" prefetch={false}> Dashboard </Link>
<Image>:内置的图片组件,对img的增强。从
next/image
导入。属性:
src:
string
,引入图片资源。- 引入本地图片时,会自动确认图片的高度
- 引入外部图片时,需手动设置宽高,以及配置资源地址白名单
width:
px
,图片宽度,不支持百分比单位height:
px
,图片高度,不支持百分比单位alt:
string
,在图像无法显示时的替代文本。fill?:
boolean
,默认:true
,让图片填充父容器大小,父容器必须分配position
,默认为absolute
。priority?:
boolean
,默认:false
,是否将图像视为高优先级,并且预加载(preload)。- 与
loading=lazy
冲突,不能同时使用 - 应该对检测为LCP(最大内容绘制)元素的任何图像使用 priority 属性。
- 仅当图像在首屏可见时,才应使用。
- 与
placeholder:
blur | empty | data:image/...
,默认:empty
,加载图像时使用的占位符。- 当
empty
时,则在加载图像时将没有占位符,只有空白区域。 - 当
blur
时,blurDataURL属性将用作占位符。 - 对于动态图像,必须提供 blurDataURL 属性。
- 当
data:image/...
时,则该dataURL 将在加载图像时用作占位符。
- 当
- tsx
<Image src="/profile.png" width={500} height={500} alt="Picture of the author" fill={true} priority={true} placeholder="blur" />
<Script>:将一个script标签添加到页面的body中(不支持在
_document.js
中使用),从next/script
中导入。属性:
src:
string
,指定外部脚本的 URL 的路径字符串。可以是绝对外部 URL 或内部路径。- tsx
import Script from 'next/script' export default function Dashboard() { return ( <> <Script src="https://example.com/script.js" /> </> ) }
Head
<Head>
组件用于管理页面的 <head>
部分,可以在其中设置文档的元数据,如标题、描述、关键字、样式表和脚本等。使用 <Head>
组件可以确保每个页面都可以独立地定义自己的 <head>
内容。
如果想要给所有页面统一添加的,那需在 pages 目录下新建 _document.js 文件来定制 HTML 页面。
作用: 方便 SEO 以及添加一些外部资源。
用法:
▸ 基本使用
import Head from 'next/head';
const MyPage = () => {
return (
<>
<Head>
<title>我的页面标题</title>
<meta name="description" content="这是我的页面描述" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
<h1>欢迎来到我的页面!</h1>
</main>
</>
);
};
export default MyPage;
▸ 避免重复标签: key
为避免<Head>
中出现重复的标签,您可以使用 key
属性,这将确保标签只呈现一次。
import Head from 'next/head'
function IndexPage() {
return (
<div>
<Head>
<title>My page title</title>
<meta property="og:title" content="My page title" key="title" />
</Head>
<Head>
<meta property="og:title" content="My new title" key="title" />
</Head>
<p>Hello world!</p>
</div>
)
}
export default IndexPage
说明:上述2个meta具有相同的key,只会渲染第二个meta标签
注意: <title>
和 <base>
标签会由 Next.js 自动检查是否有重复项,因此不需要使用 key。
_document.js
可以在~/pages/_document.js
中配置 Head 的全局配置。
特性:
_document
文件中的 Head 中不能包括<title>
标签。_document
文件中不支持<Script>
标签。
Image
<Image>:内置的图片组件,对img的增强。从 next/image
导入。
src:
string
,引入图片资源。- 引入本地图片时,会自动确认图片的高度
- 引入外部图片时,需手动设置宽高,以及配置资源地址白名单
width:
px
,图片宽度,不支持百分比单位height:
px
,图片高度,不支持百分比单位alt:
string
,在图像无法显示时的替代文本。fill?:
boolean
,默认:true
,让图片填充父容器大小,父容器必须分配position
,默认为absolute
。priority?:
boolean
,默认:false
,是否将图像视为高优先级,并且预加载(preload)。- 与
loading=lazy
冲突,不能同时使用 - 应该对检测为LCP(最大内容绘制)元素的任何图像使用 priority 属性。
- 仅当图像在首屏可见时,才应使用。
- 与
placeholder:
blur | empty | data:image/...
,默认:empty
,加载图像时使用的占位符。- 当
empty
时,则在加载图像时将没有占位符,只有空白区域。 - 当
blur
时,blurDataURL属性将用作占位符。 - 对于动态图像,必须提供 blurDataURL 属性。
- 当
data:image/...
时,则该dataURL 将在加载图像时用作占位符。
- 当
- tsx
<Image src="/profile.png" width={500} height={500} alt="Picture of the author" fill={true} priority={true} placeholder="blur" />
用法:
▸ 基本使用:加载显示本地图片
需要通过import
导入图片资源后才能使用。同时必须添加alt
属性。
▸ 加载显示网络图片
添加网络图片必须手动指定图片的width
和height
属性。宽高单位不支持百分比%,必须是 number 类型。
如果图片是外站资源,需要在next.config.js
中配置图片的域名白名单。
▸ 在next中使用原生img标签
在 next 中使用原生 img 标签需要用 picture 标签包裹之后使用,目的是方便之后做适配。
如果不想使用 picture 包裹,可以在.eslintrc.json
中关闭错误提示。
▸ priority:图片预加载
对于大图可以添加priority
预加载图片。
该属性会为渲染后的<link>
标签添加rel="preload"
属性。
▸ fill:为Image添加百分比尺寸
注意: 使用了 fill 属性后,在渲染时会生成 8 张不同尺寸的图片(有问题:实际生成的是 8 张尺寸相同的图片)。
1、父容器设置为相对定位
2、为 Image 添加 fill 属性
静态资源
全局样式
Next.js 允许在 JavaScript 文件中直接通过 import
关键字来导入 CSS 文件(不是@import )
用法:
▸ 基本使用:导入自定义样式
1、在 assets
目录或 styles
目录下编写。推荐styles
目录。
2、然后在 pages/_app.tsx
入口组件中导入
3、在组件中使用全局样式
▸ 导入第三方样式
也支持导入node_modules
中样式,导入文件后缀名不能省略。
局部样式
Next.js 默认是支持 CSS Module的,如:[name].module.css
。
CSS Module 中的选择器会自动创建一个唯一的类名。唯一类名保证在不同的文件中使用相同 CSS 类名,也不用担心冲突。
用法:
▸ 基本使用
1、在[name].module.css
或[name].module.scss
样式文件中编写局部样式
- 样式名的写法可以是
camelCase
或kebab-case
2、在组件中通过import
导入样式并使用
- 使用
camelCase
命名的样式在使用时可以通过styles.xxxYyy
语法访问。 - 使用
kebab-case
命名的样式在使用时可以通过styles["xxx-yyy"]
语法访问。
▸ 在样式中手动导入样式文件
注意: 该功能全局和局部样式都支持
1、定义样式文件,并在其中定义变量和混入
2、在另一个样式文件中使用@use
手动导入该样式文件并使用定义的变量和混入
注意: @use
可以有以下语法:
@use './xxx.scss' as *
,使用时可以直接使用$gbColor
@use './xxx.scss' as xxx
,使用时需要如下使用xxx.$gbColor
▸ 在样式中自动导入样式文件
【目前没有自动导入功能】
▸ 使用:export {}
导出变量供 JS 使用
注意: :export {}
必须要在.module.css
模块文件中使用
1、在xxx.module.scss
文件中使用:export {}
导出变量
2、在 JS 中使用import from
导入scss
文件到模块中,并在 JS 代码中使用
静态资源引用
public
public目录常用于存放静态文件,例如:robots.txt、favicon.ico、img、info.json等,并直接对外提供访问。
访问: 需以/
作为开始路径通过静态 URL访问。如添加了一张图片到public/me.png
中,通过/me.png
访问。
特性:
静态 URL 也支持在背景中使用
确保静态文件中没有与
pages/
下的文件重名,否则导致错误。
用法:
▸ 基本使用:在 Image 组件中使用
▸ 在背景中使用
▸ 直接在浏览器通过 url 访问public/info.json
中的数据
assets
assets目录常用存放样式、字体 、图片或 SVG 等文件
访问: 需要用 import
导入位于 assets 目录的文件成为模块后使用,支持相对路径和绝对路径。
// 相对路径
import Avatar from '../assets/images/avatar.png'
// 绝对路径
import Avatar from '@/assets/images/avatar.png'
特性:
- 支持在背景图片和字体中使用:
url("~/assets/images/bym.png")
,不支持@/
、import
等语法。
用法:
▸ 基本使用:使用import
导入图片并使用
▸ 在背景中使用
注意: 在背景中比较特殊,只能通过~/
开头的绝对路径的静态 URL 导入。不支持@/
、import
等语法。
字体图标
1、将字体图标存放在 assets
目录下
2、字体文件可以使用相对路径和绝对路径引用。
3、在_app.tsx
文件中全局导入字体样式
4、在页面中就可以使用字体图标了
路由
API useRouter()
useRouter():
()
,返回一个 router 对象,可以通过调用该对象中的方法实现编程导航。返回:
router:
object
,返回一个 router 对象- js
const router = useRouter()
router 属性:
router:router 对象
- pathname:
string
,当前路由文件的路径。 - query:
object
,默认:{}
,解析为对象的查询字符串,包括动态路由参数。如果页面不使用服务器端渲染,则在预渲染期间它将是一个空对象。 - basePath:
string
,活动的 basePath。 - isReady:
boolean
,路由器字段是否已在客户端更新并可供使用。只在useEffect
方法中使用。 - isPreview:
boolean
,应用程序当前是否处于预览模式。 - 其他:
asPath
、isFallback
、locale
、locales
、defaultLocale
、defaultLocales
、、
、、
、``、
- pathname:
router 方法:
router.push():
( url, as?, options?)
,页面跳转。url:
UrlObject |string
,要导航到的 URL。as?:
UrlObject |string
,路径别名,在浏览器 URL 栏中显示的路径的可选装饰器。options?:
{scroll,shallow,locale}
,配置选项。- js
// 普通导航 router.push('/about'); // 带查询参数的导航 router.push({ pathname: '/profile', query: { id: 1 } }); // 动态路由 router.push('/posts/[id]', '/posts/1'); // 使用选项 router.push('/dashboard', undefined, { shallow: true });
router.replace():
( url, as?, options?)
,页面跳转,会替换当前页面url:
UrlObject |string
,要导航到的 URL。as?:
UrlObject |string
,路径别名,在浏览器 URL 栏中显示的路径的可选装饰器。options?:
{scroll,shallow,locale}
,配置选项。- js
// 普通替换导航 router.replace('/about'); // 带查询参数的替换导航 router.replace({ pathname: '/profile', query: { id: 1 } }); // 动态路由 router.replace('/posts/[id]', '/posts/1'); // 使用选项 router.replace('/dashboard', undefined, { shallow: true });
router.back():
()
,页面返回。- js
router.back();
router.events.on():
(event, callback)
,客户端路由的监听,建议在_app.tsx 监听router.events.off():
(event, callback)
,移除客户端路由的监听,建议在_app.tsx 监听event:
string
,要监听的事件类型:'routeChangeStart'
:fn: (url, as?) => void
,当路由开始变化时触发。'routeChangeComplete'
:fn: (url, as?) => void
,当路由变化完成时触发。- 其他:
'routeChangeError'
:fn: (err, url) => void
,当路由变化失败时触发。'beforeHistoryChange'
:fn: (url) => void
,在浏览器历史记录变化之前触发。'hashChangeStart'
:fn: (url, as?) => void
,当哈希值开始变化时触发。'hashChangeComplete'
:fn: (url, as?) => void
,当哈希值变化完成时触发。
callback:
function
,当指定的事件触发时调用的函数。这个函数可以接受事件相关的参数。- js
import { useEffect } from 'react'; import { useRouter } from 'next/router'; const MyComponent = () => { const router = useRouter(); const handleRouteChange = (url) => { console.log('正在导航到:', url); }; useEffect(() => { // 添加事件监听器 router.events.on('routeChangeStart', handleRouteChange); // 清理监听器 return () => { router.events.off('routeChangeStart', handleRouteChange); }; }, [router.events]); return ( <div> <h1>我的组件</h1> </div> ); }; export default MyComponent;
router.beforePopState():
(callback)
,路由的返回和前进的监听。建议在_app.tsx 监听callback:
({url, as?, options?}) => boolean
,用于判断是否允许路由更新。返回:
true
:允许状态更新,路由将继续进行。false
:阻止状态更新,浏览器不会导航到新 URL。
- tsx
import { useEffect } from 'react'; import { useRouter } from 'next/router'; const MyComponent = () => { const router = useRouter(); useEffect(() => { - const handleBeforePopState = ({ url }) => { // 在这里添加条件判断,例如: if (url === '/restricted') { alert('您无法返回到这个页面!'); return false; // 阻止导航 } return true; // 允许导航 }; // 设置 beforePopState + router.beforePopState(handleBeforePopState); // 清理 return () => { router.beforePopState(() => true); // 重置为默认允许导航 }; }, [router]); return ( <div> <h1>我的组件</h1> <p>尝试后退浏览器历史记录。</p> </div> ); }; export default MyComponent;
新建页面
Next.js 项目页面需在 pages 目录下新建( .js
、.jsx
、 .ts
或 .tsx
)文件,该文件需导出的 React 组件。
约定式路由: Next.js 会根据 pages 目录结构和文件名,来自动生成路由,比如:
pages/index.jsx
→/
(首页, 一级路由)pages/about.jsx
→/about
(一级路由)pages/blog/index.jsx
→/blog
(一级路由)pages/blog/post.jsx
→/blog/post
(嵌套路由,一级路由)pages/blog/[slug].jsx
→/blog/:slug
(动态路由, 一级路由)
注意:
- Next.js 中没有命令行的方式创建页面。
- Next.js不需要占位组件,和 Nuxt3 不同。
用法:
▸ 新建页面步骤:
1、新建一个名为 pages/category.jsx
的组件文件,并导出(export)React 组件。
2、通过 /category
路径访问新创建的页面了。
▸ 标准模板:
1、在typescriptreact.json
中定义 next + ts 的模板
2、使用nextts
创建一个 React 组件
导航
组件导航 Link
页面之间的跳转需要用到<Link>
组件,需从 next/link 包导入。
Link 组件底层实现是一个 <a>
标签,所以使用 a + href 也支持页面切换(不推荐, 会默认刷新浏览器)。
<Link>:可以启用客户端的路由切换,从 next/link
导入。
属性:
href:
string | {pathname:'' , query : {}}
,要导航到的路径或 URL。replace?:
boolean
,默认:false
,是否替换当前 url 页面,而不是将新的 url 添加到堆栈中。target?:
_blank | _self | _parent | _top
,指定何种方式显示新页面,同a标签中的target一样。as?:
string
,在浏览器的URL栏显示的路径的别名。prefetch:
boolean
,默认:false
,当<Link>
组件进入用户的视区(最初或通过滚动)时,将发生预取。预取仅在 production 中启用。其他:scroll、shallow、locale等
- tsx
<Link href="/dashboard">Dashboard</Link> <Link href={{ pathname: '/about', query: { name: 'test' }, }}>About</Link> <Link href="/dashboard" replace>Dashboard</Link> <Link href="/dashboard" prefetch={false}> Dashboard </Link>
▸ 基本使用:href 值的类型
href 的值支持字符串、对象和 URL 链接的写法
▸ as:为路径起别名
注意: 对于对象类型的 href 写法,同样支持
▸ replace:替换当前 url 页面
编程导航 useRouter()
Next13 除了可以通过<Link>
组件来实现导航,同时也支持使用编程导航。
编程导航可以轻松的实现动态导航了,缺点就是不利于 SEO。
我们可以从 next/router 中导入 useRouter() 函数(或 class 中用 withRouer()),调用该函数可以拿到 router 对象进行编程导航。
语法: API useRouter()
用法:
▸ 参数 url:跳转地址
支持字符串、对象、外部 URL 链接路由
▸ 参数 as:路径别名
▸ router.events.on(name,fn):客户端路由监听
注意:
- 建议事件监听这种有副作用的方法写到
useEffect()
生命周期中,保证只在客户端执行。防止在服务端无法结束监听,释放变量。 events.on()
中没有 nuxt3 中的from, to
只有url
,url
相当于to
动态路由
Next.js 也是支持动态路由,并且也是根据目录结构和文件的名称自动生成。
语法:
- 页面组件目录 或 页面组件文件都 支持
[]
方括号语法(方括号前后不能有字符串)。 - 方括号里编写的字符串就是:动态路由的参数。
示例: 动态路由支持如下写法:
pages/detail/[id].tsx
->/detail/:id
pages/detail/[role]/[id].tsx
->/detail/:role/:id
pages/detail-[role]/[id].tsx
->/detail-:role/:id
(不支持)
用法:
▸ []
语法前后不能有字符串
# 不支持以下写法
pages/user-[role]/index.tsx
pages/[role]/[id]-name.tsx
▸ 访问多个动态路由参数
路由格式如下:pages/[role]/[id].tsx
路由参数
动态路由参数
传递参数:
1、通过[]
方括号语法定义动态路由,比如:/detail/[id].tsx
2、页面跳转时,在 URL 路径中传递动态路由参数,比如:/detail/10010
访问参数: 在目标页面[id].tsx
通过 router.query
获取动态路由参数
注意:
- 此处是
query
,而不是params
- 此处是
router
, 而不是route
- 如果动态路由参数和查询参数名字重复,那么动态路由参数将覆盖同名的查询字符串参数。
查询字符串参数
传递参数: 页面跳转时,通过查询字符串方式传递参数,比如:/post/detail?id=1000
访问参数: 在目标页面通过 router.query
获取查询字符串参数
404 Page
捕获 404 页面的方法:
方式一: [...slug].tsx
,捕获所有不匹配的路由,即 404 页面。
通过在方括号内添加三个点 ,如:[...slug].tsx
# 访问 pages/post/[...slug].js
/post/a # 匹配
/post/a/b # 匹配
/post # 不匹配
[...slug]
匹配的参数将作为查询参数发送到页面,并且它始终是一个数组
路由格式如下:pages/detail04/123/sdf
注意:
- 可以使用除了 slug 以外的名称,如:
[...param].tsx
。 [...slug].tsx
可以在pages
目录或其子目录中。它仅作用于该目录以及子目录。
方式二: 404.tsx
,在 pages 根目录新建 404.tsx 页面。(推荐)
注意:
404.tsx
写法只支持 pages 根目录- 还支持
500.tsx
文件,即客户端或者服务器端报错
路由匹配规则
路由匹配优先级: 预定义路由
> 动态路由
> 捕获所有路由
。
- 预定义路由:pages/post/create.js
- 将匹配 /post/create
- 动态路由 :pages/post/[pid].js
- 将匹配/post/1, /post/abc 等。
- 但不匹配 /post/create 、 /post/1/1 等
- 捕获所有路由:pages/post/[...slug].js
- 将匹配 /post/1/2, /post/a/b/c 等。
- 但不匹配/post/create, /post/abc、/post/1、、/post/ 等
中间件
middleware():(req, event?)
,用于定义中间件。这些中间件可以在请求到达页面之前进行某些处理,例如进行身份验证、重定向等。
req:
NextRequest
,提供了对请求信息的访问。- nextUrl:``,请求url(推荐使用)
- method:``,请求方法
- headers:``,请求头
- body:``,请求体(仅适用POST请求)
- cookies:``,获取cookie
- ...:``,
event?:
NextFetchEvent
,返回:
nextResponse:
NextResponse
,- NextResponse.next():``,向下一个处理中间件或页面传递请求
- NextResponse.redirect('/path'):``,重定向到另一个路径
- NextResponse.rewrite():``,重写 URL,可用于配置反向代理
- NextResponse.json({ message: 'Hello' }):``,返回自定义响应
没有返回值:``,同 NextResponse.next()一样,放行。继续处理下一个中间件。
- tsx
import { NextResponse } from 'next/server'; export function middleware(req) { const isAuthenticated = /* 检查用户认证的逻辑 */; if (!isAuthenticated) { // 重定向到登录页面 return NextResponse.redirect(new URL('/login', req.url)); } // 允许请求继续 return NextResponse.next(); } // 仅对某些路径应用中间件 export const config = { matcher: ['/protected/:path*'], // 匹配 /protected 下的所有路径 };
Next.js 的中间件允许我们去拦截客户端发起的请求,如API 请求、router 切换、资源加载、站点图片等。
拦截客户端发起的请求等等之后,便可对这些进行:重写、重定向、修改请求响应标头、或响应等操作。
对比 Nuxt3:
- Nuxt3中的中间件只能拦截 router 切换,而 Next.js 的中间件可以拦截更多的请求。
特性:
- 中间件只在服务端运行。
用法:
▸ 基本使用
1、在根目录中创建 middleware.ts
文件
2、从 middleware.ts
文件中导出一个中间件 middleware 函数(支持 async,并只允许在服务端),会接收两个参数
3、通过返回NextResponse对象来实现重定向等功能
▸ 设置 cookie
1、依赖包:cookies-next
pnpm i cookies-next
2、通过 setCookie() 方法设置 cookie
3、在中间件中通过 req.cookies.get('token').value 访问设置的 cookie
▸ 反向代理:通过返回 rewrite()实现反向代理
1、依赖包: axios
2、在组件中向http://localhost:3000
发起网络请求
3、在localhost:3000
服务器对请求进行重写。向http://codercba.com:9060
发送请求
匹配器
匹配器允许我们过滤中间件以应用特定的中间件。
语法:
matcher
可以接受一个字符串、数组或正则表达式,用于匹配请求路径。
匹配字符串
jsexport const config = { matcher: '/about' // 仅匹配 /about 路径 }
匹配数组
jsexport const config = { matcher: ['/about', '/contact'] // 匹配 /about 和 /contact 路径 }
匹配正则表达式
jsexport const config = { matcher: /^\/api\/.*$/ // 匹配所有以 /api/ 开头的路径 }
用法:
▸ 匹配动态路由:使用 :path*
来匹配动态路径部分
// middleware.js
import { NextResponse } from 'next/server'
export function middleware(req) {
// 中间件逻辑...
return NextResponse.next()
}
export const config = {
matcher: [
'/api/:path*', // 匹配 api/* 的路径
'/dashboard/:path*', // 匹配 dashboard/* 的路径
'/((?!api|_next/static|favicon.ico).*)' // 匹配不包含 api、_next、static、favicon.ico 的路径
]
}
说明:
.
:表示任意字符*
:表示 0 或 n 个:path*
:表示匹配动态路由:path
开头的路径(?!pattern)
:表示当前位置后面不跟随pattern
这个模式。
▸ 路由拦截:使用中间件+匹配器实现路由拦截
布局
Layout
Layout布局是页面的包装器,可以将多个页面的共性东西写到 Layout 布局中,使用 props.children 属性来显示页面内容。
例如:每个页面的页眉和页脚组件,这些具有共性的组件我们是可以写到一个 Layout 布局中。
▸ 基本使用:
1、在components
目录下新建 layout/index.tsx
布局组件
2、接着在_app.tsx
中通过<Layout>
组件包裹<Component>
组件
3、效果
个性化布局
实现 Cart 页有 Header 和 Footer,其他页没有。
嵌套布局
Layout 布局可以作为所有页面的容器,也可以给每个页面一个单独的布局也是可以的,并且也可以在布局中嵌套布局。
因此,我们可以利用布局再嵌套一个布局来实现二级路由。
▸ 基本使用:
1、定义嵌套布局
2、在布局<Layout>
中嵌套<NestLayout>
▸ 优化: 更加优雅的写法见下面章节
getLayout+TS
1、在组件中添加 getLayout() 静态方法,在其中返回自定义布局
// pages/index.tsx
import type { ReactElement } from 'react'
import Layout from '../components/layout'
import NestedLayout from '../components/nested-layout'
import type { IStaticProps } from './_app.tsx'
interface IProps {
children?: ReactElement
}
export const Page: FC<IProps> & IStaticProps = (props) => {
return {
/** Your content */
}
}
// 定义 getLayout()
Page.getLayout = function getLayout(page: ReactElement) {
return (
<Layout>
<NestedLayout>{page}</NestedLayout>
</Layout>
)
}
2、在_app.tsx
中将占位组件传入 getLayout()
方法中。并为getLayout()
扩展 TS 类型
// pages/_app.tsx
import type { ReactElement, ReactNode } from 'react'
import type { NextPage } from 'next'
import type { AppProps } from 'next/app'
export interface IStaticProps {
getLayout?: (page: ReactElement) => ReactNode
}
+ type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & IStaticProps
type AppPropsWithLayout = AppProps & {
+ Component: NextPageWithLayout
}
+ export default function MyApp({ Component, pageProps }: AppPropsWithLayout) {
// Use the layout defined at the page level, if available
// 调用 getLayout()
const getLayout = Component.getLayout ?? ((page) => page)
return getLayout(<Component {...pageProps} />)
}
嵌套路由
Next.js 和 Nuxt3 一样,也支持嵌套路由(只在 app 目录下),也是根据目录结构和文件的名称自动生成。
二级路由实现有两种方案:
- 方案一:使用 Layout 布局嵌套来实现
- 方案二:使用 Next.js 13 版本,新增的 app 目录(目前 beta 版本)
方案一:Layout
在一级页面/profile
中添加二级页面/profile/register
1、在二级布局nest-layout.tsx
中添加布局
2、在二级页面profile/register.tsx
中添加getLayout
属性,添加自定义布局
方案二:app目录
Next.js 13 版本,新增的app 目录(目前 beta 版本,还是处于实验性阶段,需要在配置开始)
注意:
page.tsx
、layout.tsx
和head.tsx
是固定的文件名,不能修改成其他名称。创建完
page.tsx
运行后会自动添加 2 个页面layout.tsx
和head.tsx
0、在配置中设置 app 目录为实验性功能
1、创建 app 目录
2、创建根 RootLayout 布局:layout.tsx
3、创建首页: page.tsx
4、创建 head.tsx,定制 head,用于 SEO
5、创建其它页面 cart 和布局
page.tsx
layout.tsx
6、创建 loading.tsx 页面,为加载中的页面添加 loading 效果,可以在其中添加骨架屏
7、目录结构
生命周期
组件生命周期
组件生命周期钩子在客户端和服务端的触发情况:
class 组件 | Hooks 组件 | 客户端 | 服务端 |
---|---|---|---|
constructor | useState | ✅ | ✅ |
UNSAFE_componentWillMount | - | ✅ | ✅ |
render | 函数本身 | ✅ | ✅ |
componentDidMount | useEffect | ✅ | ❌ |
componentWillUnmount | useEffect 中的返回函数 | ✅ | ❌ |
shouldComponentUpdate | useMemo | ✅ | ❌ |
componentDidUpdate | useEffect | ✅ | ❌ |
componentDidCatch | - | ✅ | ❌ |
getDerivedStateFromProps | useState 中的 update | ✅ | |
getDerivedStateFromError | - | ✅ | ❌ |
网络请求
axios封装
依赖包:
- axios
- 安装:
pnpm i axios
- 安装:
基本实现:
1、定义 HYRequest 类,并导出
2、在类中定义 request 方法
定义 request 方法,此时返回的是
Promise<unkown>
类型的数据,需要添加 TS 类型添加 TS 类型
使用 request()方法请求数据,并传入泛型参数值
3、定义 get、post 等其他方法
4、拦截器
API Routes
Next.js 提供了编写后端接口的功能(即 API Routes),编写接口可以在 pages/api
目录下编写
在 pages/api 目录下的任何 API Routes 文件都会自动映射到以 /api/*
前缀开头接口地址
基本实现:编写一个 /api/user
接口
1、在 pages/api
目录下新建 user.ts
2、在该文件中使用 handler 函数来定义接口
3、然后就可以用 fetch 函数 或 axios 轻松调用: /api/user
接口了
4、区分请求方法
渲染模式
API
- getStaticProps():
()
,- :``,
- :``,
- 返回:
- :``,
- getStaticPaths():
()
,- :``,
- :``,
- 返回:
- :``,
- getServerSideProps():
()
,- :``,
- :``,
- 返回:
- :``,
- :
()
,- :``,
- :``,
- 返回:
- :``,
认识预渲染
预渲染:默认情况下,Next.js 会预渲染每个页面,即预先为每个页面生成 HTML 文件,而不是由客户端 JavaScript 来完成。预渲染可以带来更好的性能和 SEO 效果。
Hydration:当浏览器加载一个页面时,页面依赖 JS 代码将会执行,执行 JS 代码后会激活页面,使页面具有交互性。
Next.js 具有两种形式的预渲染:
- 静态生成 (推荐):HTML 在 构建时 生成,并在每次页面请求(request)时重用。
- 服务器端渲染:在 每次页面请求(request)时 重新生成 HTML 页面。
提示:出于性能考虑,相对服务器端渲染,更 推荐 使用 静态生成 。
SSG
静态生成(也称 SSG 或 静态站点生成):如果一个页面使用了 静态生成,在 构建时 将生成此页面对应的 HTML 文件 。
这意味着在生产环境中,运行 next build 时将生成该页面对应的 HTML 文件。然后,此 HTML 文件将在每个页面请求时被重用,还可以被 CDN 缓存。
基本实现
在 Next.js 中,你可以静态生成 带有或不带有数据 的页面。接下来我们分别看看这两种情况。
生成不带数据的静态页面:
默认情况下,Next.js 使用 “静态生成” 来预渲染页面但不涉及获取数据。
注意:
- 此页面在预渲染时不需要获取任何外部数据。
- 在这种情况下,Next.js 只需在构建时为每个页面生成一个 HTML 文件即可。
▸ 基本实现:
1、新建pages/about.tsx
页面,该页面不带有数据
2、执行pnpm run build
打包页面,生成不带数据的静态页面
生成需要获取数据的静态页面:
当某些页面需要获取外部数据以进行预渲染,通常有两种情况:
- 情况一:页面 内容 取决于外部数据:使用 Next.js 提供的 getStaticProps() 函数。
- 情况二:页面 paths(路径) 取决于外部数据:使用 Next.js 提供的 getStaticPaths() 函数(通常还要同时用 getStaticProps())。
情况一:页面内容取决于外部数据
比如,发起网络请求拿到页面书籍列表的数据,并展示。
具体的使用步骤是:
接口:
baseURL
shhttp://codercba.com:9060/beike/api
获取书籍列表
jsmethod: POST path: /books params: { page?, count? }
获取书籍详情
jsmethod: GET path: /book/:id params: { id }
1、依赖包:axios
pnpm i axios
2、在service/home.ts
中定义fetchBook()
方法发送异步请求
3、在组件中定义getStaticProps()
方法
- 在
getStaticProps()
函数中借助 axios 获取到数据 - 拿到异步数据之后 return 给页面组件
- 页面就可通过 props 拿到数据来渲染页面
5、在 build 时,会执行getStaticProps()
方法,经过以上步骤,一个静态页面就打包生成
6、如果想更新该静态页面,需要运行pnpm run build
重新打包
情况二:页面 paths(路径)取决于外部数据
例如,新建一个动态路由页面,然后发起网络请求拿到书本列表,然后每本书的信息都使用单独详情页面显示。
简单的理解就是,在 build 阶段时,动态拿到 n 本书,然后根据 n 书动态生成 n 个静态详情页面。
1、在service/home.ts
中定义获取书籍详情的异步请求
2、新建pages/books-detail-ssg/[id].tsx
页面,根据书籍列表的数量动态生成页面
定义
getStaticPaths()
方法,返回结果会传递个getStaticProps()
方法的参数 context定义
getStaticProps()
方法,返回结果会传递给页面组件方法的参数 props在组件函数中根据获取到的动态数据渲染页面
3、执行pnpm run build
命令,打包生成 5 个静态页面
应用场景
建议尽可能使用静态生成(无论有 与 没有数据),因为静态生成的页面可以构建一次,并可由 CDN 提供服。
我们可以为多种类型的页面使用静态生成,包括:
- 营销页面、官网网站
- 博客文章、投资组合
- 电子商务产品列表、帮助和文档
如果在用户请求之前就可以预渲染页面,那么应该选择静态生成。反之,静态生成就不合适了。
例如,页面要显示经常更新的数据,并且页面内容会在每次请求时发生变化,这时可以这样选择:
- SSR + CSR:静态生成与客户端数据获取结合使用
- 我们可以跳过预呈现页面的某些部分,然后使用客户端 JS 来填充它们,但是客户端渲染是不利于 SEO 优化的,例如:
- 在 useEffect 中获取数据,在客户端动态渲染页面。
- 我们可以跳过预呈现页面的某些部分,然后使用客户端 JS 来填充它们,但是客户端渲染是不利于 SEO 优化的,例如:
- SSR:服务器端呈现(也称动态呈现)
- Next.js 会根据每个请求预呈现一个页面。缺点是稍微慢一点,因为页面无法被 CDN 缓存,但预渲染页面将始终是最新的。
SSR
服务器端渲染(也称 SSR 或 动态渲染):如果页面使用的是 服务器端渲染,则会在 每次页面请求时 重新生成页面的 HTML 。
要对页面使用服务器端渲染,你需要 export 一个名为 getServerSideProps() 的 async 函数。服务器将在每次页面请求时调用此函数。
基本实现
假设你的某个页面需要预渲染频繁更新的数据(从外部 API 获取)。你就可以编写 getServerSideProps 获取该数据并将其传递给 Page ,如下所示:
1、在pages/books-ssr/index.tsx
中定义getServerSideProps()
方法,用于在每次请求时为页面获取数据
2、在pages/books-ssr/index.tsx
的组件函数中根据获取的数据动态渲染页面
3、效果
路由参数
传递参数:在浏览器访问http://localhost:3000/home/123
获取参数:
方式一:在
pages/home/[id].tsx
页面的 react 函数中通过router.query
访问路由参数。方式二:在
pages/home/[id].tsx
页面的getServerSideProps()
函数的`context.params 访问路由参数
注意事项
我们知道 getStaticProps 和 getStaticPaths 函数都是在 build 阶段运行,那么 getServerSideProps 函数的运行时机是怎么样的呢?
getServerSideProps 运行时机:
- 首先,getServerSideProps仅在服务器端运行,从不在浏览器上运行。
- 如果页面使用 getServerSideProps,则:
- 当直接通过 URL 请求此页面时,getServerSideProps 在请求时运行,并且此页面将使用返回的 props 进行预渲染
- 当通过 Link 或 router 切换页面来请求此页面时,Next.js 向服务器发送 API 请求,服务器端会运行 getServerSideProps
什么时候该使用 getServerSideProps?
- 当页面显示的数据必须在请求时获取的,才应使用 getServerSideProps 。
- 如:页面需要显示经常更新的数据,并且页面内容会在每次请求时发生变化。
- 如过页面使用了 getServerSideProps 函数,那么该页面将在客户端请求时,会在服务器端渲染,页面默认不会缓存。
- 如果不需要在客户端每次请求时获取页面数据,那么应该考虑在 客户端动态渲染 或 getStaticProps.
ISR
增量静态再生(ISR,Incremental Static Regeneration):Next.js 除了支持静态生成 和 服务器端渲染,Next.js 还允许在构建网站后创建或更新静态页面。
基本实现:
比如我们继续实现前面的案例:
发起网络请求拿到页面书籍列表的数据,并展示。这次我们使用 ISR 渲染模式,让服务器端每隔 5s 重新生成静态书籍列表页面
1、在pages/books-isr/index.tsx
中通过 SSG 的方式生成一个静态页面,见:SSG
2、修改getStaticProps()
方法,在 return 的对象中添加 revalidate 属性,实现每隔多久动态生成新的静态页面
3、执行pnpm run build
命令打包页面
4、执行pnpm run start
启动打包后的页面,每隔 5s 会重新生成静态页面
CSR
CSR: Next.js 除了支持在服务器端获取数据,同时也是支持在客户端获取数据,并在客户端进行渲染。
在客户端获取数据,需要在页面组件或普通组件的 useEffect() 函数中获取,比如:
1、在pages/books-csr/index.tsx
中通过 useEffect()
获取数据